#if defined(_MSC_VER)
#include "SDL.h"
#else
#include "SDL/SDL.h"
#endif

#include "BaseGameEngine.h"
#include "ThreeDCubeGame.h"
#include "ThreeDCubePlayer.h"
#include "ThreeDCubeGameEnemy.h"
#include <fstream>
#include <iostream>
using namespace std;

/**
Constructor
*/
ThreeDCubeGame::ThreeDCubeGame()
:	BaseGameEngine( 6 )
, m_bPaused(true)
, m_iColourChangesLeft(0)
, m_iLivesLeft(3)
, m_iLevelNumber(0)
, m_iScore(0)
, m_iPowerTime(10000)
, m_iPowerStatus(false)
, m_iImmunityTime(10000)
, m_iImmunityStatus(false)
, m_iFreezeTime(10000)
, m_iFreezeStatus(false)
, m_iHyperSpeed(false)
, pausetype(0)
, temp(3)
{
	InitialiseLevel();
		
}

//return hi score value inside file
int ThreeDCubeGame::GetHiScore() 
{
	int a;
	ifstream fin;
	fin.open("score.txt");
	//handle exception and exit program
	if(fin.fail())
	{
		cout<<"Error: No file exists to track hiscore between game sessions. Make score.txt file."<<endl;
		fin.close();
		exit(0);
	}
	else 
	{
		fin >> a;
		fin.close();
	}	
	return a;
}

//set hi score value to file
void ThreeDCubeGame::SetHiScore() 
{
	int a;
	ofstream fout;
	fout.open("score.txt");
	if(fout.fail())
	{
		cout<<"Error: No file exists to track hiscore between game sessions. Make score.txt file."<<endl;
		fout.close();
		exit(0);
	}
	fout << m_iScore; 
	fout.close();
}

//if player lands on power, it is activated
bool ThreeDCubeGame::PowerUp(bool m_iPowerStatus)
{
	return m_iPowerStatus;
}

//if player lands on immunity, it is activated
bool ThreeDCubeGame::Immunity(bool m_iImmunityStatus)
{
	return m_iImmunityStatus;
}

//if player lands on freeze enemy motion tile, it is activated
bool ThreeDCubeGame::FreezeEnemy(bool m_iImmunityStatus)
{
	return m_iFreezeStatus;
}

//used to load maps from files
void ThreeDCubeGame::LoadFile() 
{
	char ch;
	ifstream fin;
	//select level
	switch(m_iLevelNumber){
		case 0:	fin.open("level1.txt");break;
		case 1: fin.open("level2.txt");break;
		case 2: fin.open("level3.txt");break;
	}
	if(fin.fail())
	{
		cout<<"Error: File for this level does not exist. Please check again."<<endl;
		fin.close();
		exit(0);
	}
	//used to setmap entry to draw map for level selected
	int entry_x=0;
	int entry_y=0;
	while( fin.get(ch) )
	{
		//ignore newline when scanning file for characters
		if( ch=='\n' )
		{
			entry_x=0;
			entry_y++;
		}
		else
		{
			SetMapEntry( entry_x, entry_y, ch );
			entry_x++;
			
		}
	}
	fin.close();
}

int ThreeDCubeGame::InitialiseObjects()
{
	// Destroy any existing objects
	DestroyOldObjects();

	// Create an array one element larger than the number of objects that you want.
	m_ppDisplayableObjects = new DisplayableObject*[18];
	
	//set players depending on level number
	switch( m_iLevelNumber )
	{
	case 0:
	default:
		m_ppDisplayableObjects[0] = new ThreeDCubePlayer(this, 1, 1 );
		m_ppDisplayableObjects[1] = new ThreeDCubeGameEnemy(this, 12, 6, 1, 1 );
		m_ppDisplayableObjects[2] = new ThreeDCubeGameEnemy(this, 12, 6, 3, 3 );
		m_ppDisplayableObjects[3] = NULL;
		return 3;
		break;
	case 1:
		m_ppDisplayableObjects[0] = new ThreeDCubePlayer(this, 1, 1 );
		m_ppDisplayableObjects[1] = new ThreeDCubeGameEnemy(this, 12, 6, 1, 1 );
		m_ppDisplayableObjects[2] = new ThreeDCubeGameEnemy(this, 12, 6, 2, 2 );
		m_ppDisplayableObjects[3] = new ThreeDCubeGameEnemy(this, 4, 6, 3, 3 );
		m_ppDisplayableObjects[4] = NULL;
		return 4;
		break;
	case 2:
		m_ppDisplayableObjects[0] = new ThreeDCubePlayer(this, 1, 1 );
		m_ppDisplayableObjects[1] = new ThreeDCubeGameEnemy(this, 12, 6, 1, 1 );
		m_ppDisplayableObjects[2] = new ThreeDCubeGameEnemy(this, 12, 6, 2, 2 );
		m_ppDisplayableObjects[3] = new ThreeDCubeGameEnemy(this, 4, 6, 3, 3 );
		m_ppDisplayableObjects[4] = new ThreeDCubeGameEnemy(this, 8, 8, 4, 4 );
		m_ppDisplayableObjects[5] = new ThreeDCubeGameEnemy(this, 32, 8, 1, 5 );
		m_ppDisplayableObjects[6] = new ThreeDCubeGameEnemy(this, 27, 1, 3, 6 );
		m_ppDisplayableObjects[7] = new ThreeDCubeGameEnemy(this, 27, 1, 2, 7 );
		m_ppDisplayableObjects[8] = NULL;
		return 8;
		break;
	}
}

/**
Do any setup of back buffer prior to locking the screen buffer
Basically do the drawing of the background in here and it'll be copied to the screen for you as needed
*/
void ThreeDCubeGame::SetupBackgroundBuffer()
{
	FillBackground( 0 );
	for ( int iX = 0 ; iX < GetScreenWidth() ; iX++ )
		for ( int iY = 0 ; iY < this->GetScreenHeight() ; iY++ )
			switch( rand()%100 )
			{
				case 0: SetBackgroundPixel( iX, iY, 0xFF0000 ); break;
				case 1: SetBackgroundPixel( iX, iY, 0x00FF00 ); break;
				case 2: SetBackgroundPixel( iX, iY, 0x0000FF ); break;
				case 3: SetBackgroundPixel( iX, iY, 0xFFFF00 ); break;
				case 4: SetBackgroundPixel( iX, iY, 0x00FFFF ); break;
				case 5: SetBackgroundPixel( iX, iY, 0xFF00FF ); break;
			}

	// Draw the cubes for the current map
	for ( int y = 0 ; y < MAP_HEIGHT ; y++ )
		for ( int x = 0 ; x < MAP_WIDTH ; x++ )
			if ( GetMapEntry( x, y ) != 'X' )
				DrawCube( GetScreenXForMapX(x), GetScreenYForMapY(y), false, this->GetMapEntry(x,y) );
	//drawcube method modified to check what block type is being considered
}

/**
Draw a cube within a rectangular space, with the top-left corner at a specific position.
If bCopyPixelsAndRedraw is true then the background is copied to the foreground and the screen is redrawn.
*/
void ThreeDCubeGame::DrawCube( int iXOffset, int iYOffset, bool bCopyPixelsAndRedraw, char tileColour )
{
	const int iSize = 30;
	//changed the basic block colour to bluish purple
	int iColourTop = 0x6600FF;
	const int iColourLeft = 0x0000FF;
	const int iColourRight = 0xD600D6;
	const int iColourB = 0xFF7623;
	const int iColourC = 0xFF76ff;

	//different block colours
	switch( tileColour ) 
	{
		case 'B': iColourTop = 0xFF76ff;break;
		case 'C': iColourTop = 0xFFE47A;break;
		case 'D': iColourTop = 0xFFCC00;break;
		case 'E': iColourTop = 0xCC9900;break;
		case 'F': iColourTop = 0x660033;break;
		case 'H': iColourTop = 0x9999FF;break;//light blue for hyper speed tile; colour signifies "speed"
		case 'I': iColourTop = 0xFF0000;break;//red makes immune; colour signifies "alarm"
		case 'P': iColourTop = 0x00FF00;break;//green gives bonus; colour signifies "go-ahead"
		case 'S': iColourTop = 0x0099FF;break;//blue is safe heaven; colour signifies "heaven"
		case 'L': iColourTop = 0xFFFFFF;break;//white is to add life; colour signifies "hospital"
		case 'M': iColourTop = 0x7A7A7A;break;//grey to freeze; colour signifies "icy" 
	}

	int x,y;

	for ( y = 0 ; y <= iSize ; y++ )
	{
		// Draw the top face:
		for ( x = iSize-y ; x <= iSize+y ; x++ )
		{
			SetBackgroundPixel( iXOffset + x, iYOffset + y, iColourTop );
			SetBackgroundPixel( iXOffset + x, iYOffset + iSize*2 - y, iColourTop );
		}
		// The left face
		for ( x = 0 ; x < y ; x++ )
			SetBackgroundPixel( iXOffset + x, iYOffset + iSize + y, iColourLeft );
		for ( x = y ; x <= iSize ; x++ )
			SetBackgroundPixel( iXOffset + x, iYOffset + iSize*2 + y, iColourLeft );
		// The right face
		for ( x = iSize + y ; x <= iSize*2 ; x++ )
			SetBackgroundPixel( iXOffset + x, iYOffset + iSize*2 - y, iColourRight );
		for ( x = iSize ; x <= iSize*2-y ; x++ )
			SetBackgroundPixel( iXOffset + x, iYOffset + iSize*2 + y, iColourRight );
	}

	// If the screen changes after initialisation then we need to copy the pixels to the screen.
	if ( bCopyPixelsAndRedraw )
	{
		CopyBackgroundPixels( iXOffset, iYOffset, iSize*2, iSize*3 );
		SDL_Rect* pRect = GetNextUpdateRect();
		pRect->x = iXOffset;
		pRect->y = iYOffset;
		pRect->w = iSize*2;
		pRect->h = iSize*3;
	}
}

/**
Handle any key presses here.
Note that the objects themselves (e.g. player) may also check whether a key is pressed
*/
void ThreeDCubeGame::KeyDown(int iKeyCode)
{
	switch ( iKeyCode )
	{
	case SDLK_ESCAPE: // End program when escape is pressed
		SetExitWithCode( 0 );
		break;
	case SDLK_SPACE: //unpause or pause game when you press space
		m_bPaused=!m_bPaused;
		//default pause message
		pausetype=0;
		SetupBackgroundBuffer();
		Redraw(true);
		break;
	}
}

void ThreeDCubeGame::DrawStrings()
{
	using namespace std;
	char buf[128];

	// First clear the area where they were originally drawn - i.e. undraw previous text
	CopyBackgroundPixels( 0, 0, 1000, 45 );

	sprintf( buf, "Cubes: %3d", m_iColourChangesLeft );
	DrawString( 50, 5, buf, 0xffff00);

	sprintf( buf, "Lives: %3d", m_iLivesLeft );
	DrawString( 50, 25, buf, 0xffff00);
	
	sprintf( buf, "Score: %3d", m_iScore );
	DrawString( 200, 5, buf, 0xffff00);

	sprintf( buf, "HiScore: %3d", GetHiScore() );
	DrawString( 200, 25, buf, 0xffff00);

	sprintf( buf, "Power Validity: %3d", m_iPowerTime/1000 );
	DrawString( 450, 5, buf, 0xffff00);

	sprintf( buf, "Immunity Validity: %3d", m_iImmunityTime/1000 );
	DrawString( 450, 25, buf, 0xffff00);

	sprintf( buf, "Freeze Enemy Validity: %3d", m_iFreezeTime/1000 );
	DrawString( 450, 45, buf, 0xffff00);

	SDL_Rect* pRect = GetNextUpdateRect();
	pRect->x = 0;
	pRect->y = 0;
	pRect->w = 1000;
	pRect->h = 45;
}

void ThreeDCubeGame::DrawScreen()
{
	//pause game screen
	if(m_bPaused==true)
	{
		FillBackground(0xFFFF00);
		CopyAllBackgroundBuffer();
		
		if(pausetype==0)
			DrawString(400,300,"Press space to start.", 0x000000);
		
		if(pausetype==1)
			DrawString(350,300,"You lost a life. Press space to continue.", 0x000000);
			
		if(pausetype==2)
			DrawString(300,300,"Proceed to next level. Press space to continue.", 0x000000);
			
		if(pausetype==3)
			DrawString(300,300,"Game over. You lost. Press space to restart game.", 0x000000);
		
		if(pausetype==4)
			DrawString(300,300,"You won the game! Press space to restart game.", 0x000000);

		if(pausetype==5)
			DrawString(300,300,"HIGH SCORE! Press space to restart game.", 0x000000);	

		Redraw(true);
	}
	else {
	// First draw the background
	CopyAllBackgroundBuffer();
	// Then draw the changing objects
	DrawChangingObjects();
	// And finally, draw the text
	DrawStrings();
	}
}

void ThreeDCubeGame::InitialiseLevel()
{
	// Destroy any moving objects for the current level - releasing the memory
	DestroyOldObjects();
	
	//below occurs only when all lives have been lost, to load level 1
	LoadFile();
	//Redraw(true);

	// Count the number of colour cubes to change
	m_iColourChangesLeft = 0;
	for ( int y = 0 ; y < MAP_HEIGHT ; y++ )
		for ( int x = 0 ; x < MAP_WIDTH ; x++ )
		{
			switch( m_acMapData[x][y] )
			{
			case 'B': m_iColourChangesLeft++; break;
			case 'C': m_iColourChangesLeft=m_iColourChangesLeft+2; break;				
			case 'D': m_iColourChangesLeft=m_iColourChangesLeft+3; break;				
			case 'E': m_iColourChangesLeft=m_iColourChangesLeft+4; break;
			case 'F': m_iColourChangesLeft=m_iColourChangesLeft+5; break;
			case 'P': m_iColourChangesLeft;break;
			case 'H': m_iColourChangesLeft;break;
			case 'L': m_iColourChangesLeft;break;				
			case 'S': m_iColourChangesLeft;break;				
			case 'M': m_iColourChangesLeft;break;
			}
		}
	// Create the moving objects for the new level
	InitialiseObjects();
}

void ThreeDCubeGame::GameAction()
{
	//if game paused, then stop all movement
	if( m_bPaused == true ) 
	{
		this->DrawScreen();
		return;
	}

	//if powered up, then bonus score only valid for a fixed time
	if( PowerUp(m_iPowerStatus) == true ) 
	{
		//when time is over, no more bonus score
		if(m_iPowerTime == 0)
		{
			m_iPowerStatus=false;
			m_iPowerTime=10000;
		}
		else
			m_iPowerTime=m_iPowerTime-10;
		Redraw(true);
	}
	
	//if under immunity, then immunity only valid for a fixed time
	if( Immunity(m_iImmunityStatus) == true ) 
	{		
		//when time is over, no more immunity	
		if(m_iImmunityTime == 0)
		{
			m_iImmunityStatus=false;
			m_iImmunityTime=10000;
		}
		else
			m_iImmunityTime=m_iImmunityTime-10;
		Redraw(true);
	}

	//if enemies are frozen, then this power only valid for a fixed time
	if( FreezeEnemy(m_iFreezeStatus) == true ) 
	{
		//when time is over, no more power
		if(m_iFreezeTime == 0)
		{
			m_iFreezeStatus=false;
			m_iFreezeTime=10000;
		}
		else
			m_iFreezeTime=m_iFreezeTime-10;
		Redraw(true);
	}

	// If too early to act then do nothing
	if ( !TimeToAct() )
		return;

	// Don't act for another 10 ticks
	SetTimeToAct( 10 );

	UpdateAllObjects( GetTime() );

	//if no more lives, restart
	if ( m_iLivesLeft <= 0 )
	{ 
		if(m_iScore > GetHiScore())
		{
			SetHiScore();
			pausetype=5;
			m_bPaused=true;

		}
		m_iScore=0;
		m_iLivesLeft=3;
		m_iLevelNumber=0;
		pausetype=3;
		m_bPaused=true;
		InitialiseLevel();
		// Set up the initial background - with the new tiles drawn on it.
		SetupBackgroundBuffer();
		Redraw(true); // Force redraw of all of screen
		}

	// If no more tiles to change then player wins the level.
	if ( m_iColourChangesLeft <= 0 )
	{ 
		// Start the next level
		m_iLevelNumber++;
		//stop all bonus effects
		m_iPowerTime=10000;
		m_iPowerStatus=false;
		m_iImmunityTime=10000;
		m_iImmunityStatus= false;
		m_iFreezeTime=10000;
		m_iFreezeStatus= false;
		m_iHyperSpeed=false;
		m_bPaused=true;
		pausetype=2;
		//if you complete game, restart to level 1
		if ( m_iLevelNumber >= NUMBER_MAPS )
		{
			if(m_iScore > GetHiScore())
			{
				SetHiScore();
				pausetype=5;
				m_bPaused=true;
			}
			m_iLevelNumber = 0;
			m_iLivesLeft = 3;
			m_iScore = 0;
			pausetype=4;
			m_bPaused=true;
		}
		InitialiseLevel();
		// Set up the initial background - with the new tiles drawn on it.
		SetupBackgroundBuffer();
		Redraw(true); // Force redraw of all of screen
	}
}